/*
 * Copyright 2011 Clean GWT
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package com.cleangwt.user.client.ui;

import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.user.client.ui.ComplexPanel;
import com.google.gwt.user.client.ui.HasHorizontalAlignment.HorizontalAlignmentConstant;
import com.google.gwt.user.client.ui.HasVerticalAlignment.VerticalAlignmentConstant;
import com.google.gwt.user.client.ui.Widget;

/**
 * This class provides methods for laying widgets in-line with base table elements {@code <tr>}
 * {@code <td>} and {@code <th>}
 * 
 * @author Jess
 * @version 1.0
 * @since 1.0
 */
public class TablePanel extends ComplexPanel {

  /**
   * The layout cell for {@link TablePanel}.
   */
  public static class TableLayoutCell {
    private final Object layoutData;

    protected TableLayoutCell(Object layoutData) {
      this.layoutData = layoutData;
    }
  }

  /**
   * The class provides horizontal alignment to tell {@link TablePanel} to set the horizontal
   * alignment for parent element(TD or TH) that the layout data to add to.
   */
  public static class HorizontalAlignCell extends TableLayoutCell {
    private final HorizontalAlignmentConstant align;

    HorizontalAlignCell(HorizontalAlignmentConstant align, Object layoutData) {
      super(layoutData);
      this.align = align;
    }
  }

  /**
   * The class provides vertical alignment to tell {@link TablePanel} to set the vertical alignment
   * for parent element(TD or TH) that the layout data to add to.
   */
  public static class VerticalAlignCell extends TableLayoutCell {
    private final VerticalAlignmentConstant align;

    VerticalAlignCell(VerticalAlignmentConstant align, Object layoutData) {
      super(layoutData);
      this.align = align;
    }
  }

  /**
   * This class provides a 'colSpan' to tell {@link TablePanel} to do col span when this layout cell
   * is added.
   */
  public static class ColSpan extends TableLayoutCell {

    private final int spans;

    /**
     * Creates a column span with the specified span count and layout data.
     * 
     * @param spans the column span count
     * @param the layout data to be added to spanned cell
     */
    ColSpan(int spans, Object layoutData) {
      super(layoutData);
      this.spans = spans;
    }

  }

  /**
   * This class provides a 'rowSpan' to tell {@link TablePanel} to do row span when this layout cell
   * is added.
   */
  public static class RowSpan extends TableLayoutCell {

    private final int spans;

    /**
     * Creates a column span with the specified span count and layout data.
     * 
     * @param spans the row span count
     * @param the layout data to be added to spanned cell
     */
    RowSpan(int spans, Object layoutData) {
      super(layoutData);
      this.spans = spans;
    }

  }

  private static final String DEFAULT_STYLENAME = "gwt-TablePanel";
  private static final String BORDERED_STYLENAME = "bordered";

  private Element colGroup = Document.get().createColGroupElement();

  /**
   * Creates a table panel.
   */
  public TablePanel() {
    setElement(Document.get().createTableElement());
    this.getElement().appendChild(colGroup);
    setStyleName(DEFAULT_STYLENAME);
  }

  /**
   * Creates a table panel with the given 'bordered' option.
   * 
   * @param bordered whether to add 'bordered' style
   */
  public TablePanel(boolean bordered) {
    this();
    if (bordered) {
      addStyleName(BORDERED_STYLENAME);
    }
  }

  /**
   * Adds a row with the given layout data that must in types of {@link Widget}, {@link String} and
   * {@link TableLayoutCell}.
   * 
   * @param layoutData the layout data
   */
  public void addRow(Object... layoutData) {
    Element row = Document.get().createTRElement();
    getElement().appendChild(row);
    for (Object data : layoutData) {
      addLayoutData(row, data);
    }
  }

  /**
   * Sets with for the specified column.
   * 
   * @param column the column
   * @param width the width
   */
  public void setColumnWidth(int column, String width) {
    int newCols = column - colGroup.getChildCount() + 1;
    for (int i = 0; i < newCols; i++) {
      colGroup.appendChild(Document.get().createColElement());
    }
    Element col = colGroup.getChild(column).cast();
    col.setAttribute("width", width);
  }

  /**
   * Adds layout data to the specified row.
   * 
   * @param row the row element
   * @param layoutData the layout data
   * @return the TD(or TH) element
   */
  private Element addLayoutData(Element row, Object layoutData) {
    if (layoutData == null) {
      return null;
    }
    if (layoutData instanceof Widget) {
      Element td = Document.get().createTDElement();
      row.appendChild(td);
      this.add((Widget) layoutData, td);
      return td;
    } else if (layoutData instanceof String) {
      Element td = Document.get().createTHElement();
      row.appendChild(td);
      td.setInnerHTML((String) layoutData);
      return td;
    } else if (layoutData instanceof TableLayoutCell) {
      Element td = this.addLayoutData(row, ((TableLayoutCell) layoutData).layoutData);
      if (layoutData instanceof ColSpan) {
        td.setPropertyInt("colSpan", ((ColSpan) layoutData).spans);
      } else if (layoutData instanceof RowSpan) {
        td.setPropertyInt("rowSpan", ((RowSpan) layoutData).spans);
      } else if (layoutData instanceof HorizontalAlignCell) {
        HorizontalAlignmentConstant align = ((HorizontalAlignCell) layoutData).align;
        td.setPropertyString("align", align.getTextAlignString());
      } else if (layoutData instanceof VerticalAlignCell) {
        VerticalAlignmentConstant align = ((VerticalAlignCell) layoutData).align;
        td.getStyle().setProperty("verticalAlign", align.getVerticalAlignString());
      }
      return td;
    } else {
      throw new IllegalArgumentException();
    }
  }
}
